package org.luosl.webmagicx.conf

import scala.xml.{Node, Text}

/**
  * Created by luosl on 2018/1/3.
  */
class XmlProps(val dataList:List[(String, Any)], val tag:String) {

  /**
    * 获取最后一个有效属性的 option
    * @param matchers keys
    */
  def valueOption[T](matchers:Matcher*)(tag:Class[T]): Option[T] = asOpt(extract(matchers:_*), tag)

  /**
    * 获取 props对象
    * @param matchers keys
    * @return
    */
  def props(matchers: Matcher*) :List[XmlProps] = {
    extract(matchers:_*).filter(_._2.isInstanceOf[List[_]]).map{ tu=>
      val compName:String = tu._1
      XmlProps(tu._2.asInstanceOf[List[(String,Any)]], compName)
    }
  }

  /**
    * 抽取属性列表
    * @param matchers key 路径
    * @return
    */
  private def extract(matchers:Matcher*): List[(String, Any)] = {
    val matchingItems:List[(String, Any)] = matchers match {
      case Seq() => List.empty
      case Seq(matcher) => dataList.filter(tag=> matcher.isMatch(tag._1))
      case _ =>
        val headList:List[(String, Any)] = dataList.filter(tag=> matchers.head.isMatch(tag._1))
        matchers.tail.foldLeft(headList){ (list, matcher)=>
          val values:List[(String, Any)] = list.map(_._2)
            .filter(_.isInstanceOf[List[_]])
            .flatMap(_.asInstanceOf[List[(String, Any)]])
          if(values.nonEmpty) values.filter(tag=> matcher.isMatch(tag._1)) else List.empty
        }
    }
    matchingItems.reverse
  }

  /**
    * 强转类型
    * @param values values
    * @param types types
    */
  private def asOpt[T](values:List[(String, Any)], types:Class[T]): Option[T] ={
    val strValueOpt:Option[String] = values.lastOption.map(_._2.toString)
    try {
      val result:Any = types match {
        case PropType.strType => strValueOpt
        case PropType.intType => strValueOpt.map(_.toInt)
        case PropType.longType => strValueOpt.map(_.toLong)
        case PropType.doubleType => strValueOpt.map(_.toDouble)
        case PropType.booleanType => strValueOpt.map(_.toBoolean)
        case _ =>
          throw PropsException(s"无效的 props type : ${types.getSimpleName}")
      }
      result.asInstanceOf[Option[T]]
    }catch {
      case e:Exception =>
        throw PropsException(s"${strValueOpt.get} 无法转换为类型: ${types.getSimpleName}", e)
    }
  }

  /**
    * 获取一个最后一个有效属性，如果该属性不存在则抛出异常
    * @param matchers keys
    * @return
    */
  def value[T](matchers:Matcher*)(types:Class[T]):T = {
    val result:T = valueOption(matchers:_*)(types).getOrElse(
      throw PropsException(s"组件:$tag，" +
      s"[tag=$tag]需要必要的属性[${matchers.mkString(" ")}]")
    )
    result
  }

  /**
    * 获取一个最后一个有效属性，当属性不存在时则给出默认值
    * @param matchers keys
    * @param defValue defValue
    * @return
    */
  def valueOrDefault[T](matchers: Matcher*)(types:Class[T])(defValue:T):T = {
    valueOption(matchers:_*)(types).getOrElse(defValue)
  }

  /**
    * 获取一个属性，当属性不存在时则给出Null
    * @param matchers keys
    * @return
    */
  def valueOrNull[T](matchers: Matcher*)(types:Class[T]):T = valueOrDefault(matchers:_*)(types)(null.asInstanceOf[T])

}

object XmlProps{

  def apply(dataList: List[(String, Any)], tag: String): XmlProps = new XmlProps(dataList, tag)

  def apply(node: Node, tag: String): XmlProps = {
    /**
      * 抽取属性
      * @param node node
      * @return
      */
    def extractProp(node:Node):List[(String, Any)] = {
      val childNodes:Seq[Node] = node.child.filter(!_.isInstanceOf[Text])
      val propList:List[(String, Any)] = node.attributes.flatMap(_.asAttrMap).toMap.toList
      val attrList:List[(String, Any)] = if(childNodes.isEmpty)  ("text"-> node.text.trim) :: propList else propList
      childNodes.foldLeft(attrList){ (ls, cNode) =>
        (cNode.label -> extractProp(cNode)) :: ls
      }
    }
    val dataList:List[(String, Any)] = if(null == node) List.empty else extractProp(node)
    new XmlProps(dataList, tag)
  }

  def apply(tag: String): XmlProps = new XmlProps(null, tag)

  def unapply(props: XmlProps): Option[(List[(String,Any)], String)] =  if(null == props) None else Option(props.dataList, props.tag)
}

/**
  * props 的属性类型
  */
object PropType{
  val intType:Class[Int] = classOf[Int]
  val longType:Class[Long] = classOf[Long]
  val doubleType:Class[Double] = classOf[Double]
  val strType:Class[String] = classOf[String]
  val booleanType:Class[Boolean] = classOf[Boolean]
  val propType:Class[XmlProps] = classOf[XmlProps]
}

/**
  * prop key 匹配器
  */
trait Matcher {
  def isMatch(propKey: String): Boolean
}

/**
  * Matcher 的隐式转换
  */
object MatcherConverters{
  implicit def str2EqualMatcher(str:String):Matcher = EqualMatcher(str)
}

/**
  * 完全相等匹配
  * @param key key
  */
case class EqualMatcher(key:String) extends Matcher{
  override def isMatch(propKey: String): Boolean = {
    key match {
      case "*" => true
      case _ =>  propKey == key
    }
  }
}

/**
  * 后缀匹配
  * @param key key
  */
case class SuffixMatcher(key:String) extends Matcher{
  override def isMatch(propKey: String): Boolean = propKey.toLowerCase.endsWith(key)
}

case class PropsException(msg:String, t: Throwable = new Throwable) extends RuntimeException(msg, t)

